package tazmania.entity;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.glu.gl2.GLUgl2;

import tazadum.engine.Entity;
import tazadum.engine.Interval;
import tazadum.engine.Signal;
import tazadum.engine.math.Hexagon;
import tazadum.engine.math.Vector;
import tazadum.engine.util.BufferUtil;

public class HexagonStripEntity implements Entity {
	private static final int WIDTH = 25;
	private static final int HEIGHT = 10;

	private static final float RADIUS = 3;
	private static final float SPACING = RADIUS * 0.1f;
	private static final float SLANT_HEIGHT = 0.5f;
	private static final int VERT_PER_OBJ = 19;

	private final Signal signal;

	private IntBuffer indices;
	private FloatBuffer vertices;
	private FloatBuffer colors;

	protected GLUgl2 glu = new GLUgl2();

	protected final float[] ambient = { 0.01f, 0.1f, 0.01f, 1.0f };
	protected final float[] diffuse = { 0.8f, 0.8f, 0.8f, 1.0f };
	protected final float[] specular = { 1.0f, 1.0f, 1.0f, 1.0f };
	protected final float[] light2_pos = { 1.0f, 1.0f, 10.0f, 1.0f };
	private final float[] colorGray = { .5f, .5f, .5f };

	protected final Vector up = Vector.Y;
	protected final Vector eye = new Vector(10, 10, 10);
	protected final Vector target = new Vector(0, 0, 0);

	private final List<Vector[]> template = new LinkedList<Vector[]>();
	private float screenRatio;

	public HexagonStripEntity(final Signal signal) {
		this.signal = signal;
	}

	@Override
	public void precalc(final GLAutoDrawable drawable, final float screenRatio) {
		this.screenRatio = screenRatio;
		colors = BufferUtil.newFloatBuffer(3 * WIDTH * HEIGHT);
		colors.rewind();

		final int objects = WIDTH * HEIGHT;

		vertices = BufferUtil.newFloatBuffer(3 * objects * VERT_PER_OBJ);
		vertices.rewind();

		final float hexagonHeight = Hexagon.side(RADIUS + SPACING);
		for (int y = 0; y < HEIGHT; y++) {
			for (int x = 0; x < WIDTH; x++) {
				final float alpha = x / (float) WIDTH;
				final float curve = (float) Math.sin(alpha * 5f) * 15 - 20;

				final float adjustedX = x - ((y & 1) * 0.5f) - 8;
				final Vector center = new Vector(adjustedX * 3 * (RADIUS + SPACING), curve + y * hexagonHeight, 0);
				final Vector[] vectors = new Vector[7];

				// Bottom
				center.z = -1;
				Hexagon.generate(vertices, center, RADIUS);

				// add to template
				Hexagon.generate(vectors, center, RADIUS);
				vectors[6] = center;
				template.add(vectors);
				// Center
				center.z = -SLANT_HEIGHT;
				Hexagon.generate(vertices, center, RADIUS);
				// Top
				center.z = 0;
				Hexagon.generate(vertices, center, RADIUS * 0.85f);

				vertices.put(center.x);
				vertices.put(center.y);
				vertices.put(center.z);
			}
		}

		vertices.rewind();
		colors.rewind();

		indices = BufferUtil.newIntBuffer(4 * objects * (6 + 6 + 3));
		indices.rewind();
		for (int object = 0; object < objects; object++) {
			final int offset = object * VERT_PER_OBJ;
			for (int i = 0; i < 6; i++) {
				final int iplus1 = (i + 1) % 6;

				// bottom quad
				indices.put(offset + i);
				indices.put(offset + i + 6);
				indices.put(offset + iplus1 + 6);
				indices.put(offset + iplus1);

				// slant quad
				indices.put(offset + 6 + i);
				indices.put(offset + 6 + i + 6);
				indices.put(offset + 6 + iplus1 + 6);
				indices.put(offset + 6 + iplus1);
			}

			// top
			for (int i = 0; i < 6; i += 2) {
				final int iplus1 = (i + 1) % 6;
				final int iplus2 = (i + 2) % 6;
				indices.put(offset + 12 + i);
				indices.put(offset + 12 + iplus1);
				indices.put(offset + 12 + iplus2);
				indices.put(offset + VERT_PER_OBJ - 1);
			}
		}
		indices.rewind();
	}

	@Override
	public void update(final GLAutoDrawable drawable, final float globalTime, final float localTime, final float dt, final Interval interval) {
		final GL2 gl = drawable.getGL().getGL2();

		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPushMatrix();
		glu.gluPerspective(110, screenRatio, 0.1f, 1000);

		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPushMatrix();
		gl.glLoadIdentity();

		final float time = localTime * 5;

		eye.x = -15;
		eye.y = 0;
		eye.z = 20;
		target.x = -8;
		target.z = 0;
		target.y = 0;
		glu.gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, Vector.Y.x, Vector.Y.y, Vector.Y.z);

		final float R = signal.channel(Signal.CHANNEL_R);
		final float G = signal.channel(Signal.CHANNEL_G);
		final float B = signal.channel(Signal.CHANNEL_B);
		specular[0] = R;
		specular[1] = G;
		specular[2] = B;

		light2_pos[0] = (float) Math.sin(time) * 35 + 20;
		light2_pos[1] = (float) Math.sin(time * 0.53) * 20 + 15;

		animateHexagons(time);

		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glShadeModel(GL2.GL_FLAT);
		gl.glClear(GL.GL_DEPTH_BUFFER_BIT);

		setupLight2(gl);

		gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_AMBIENT_AND_DIFFUSE, colorGray, 0);
		gl.glMaterialfv(GL.GL_FRONT_AND_BACK, GL2.GL_SPECULAR, colorGray, 0);
		gl.glMaterialf(GL.GL_FRONT_AND_BACK, GL2.GL_SHININESS, 1);

		// render
		gl.glEnableClientState(GL2.GL_VERTEX_ARRAY);
		gl.glVertexPointer(3, GL2.GL_FLOAT, 0, vertices);
		gl.glDisableClientState(GL2.GL_COLOR_ARRAY);
		gl.glColor4f(.5f, .5f, .5f, 1);
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL2.GL_FILL);
		gl.glDrawElements(GL2.GL_QUADS, WIDTH * HEIGHT * 4 * (6 * 2 + 3), GL.GL_UNSIGNED_INT, indices);

		// shift
		vertices.rewind();
		colors.rewind();

		gl.glDisableClientState(GL2.GL_VERTEX_ARRAY);

		gl.glDisable(GL2.GL_LIGHT1);
		gl.glDisable(GL2.GL_LIGHTING);
		gl.glDisable(GL.GL_DEPTH_TEST);

		// gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glPopMatrix();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glPopMatrix();
	}

	private void animateHexagons(final float time) {
		vertices.rewind();
		final Iterator<Vector[]> iterator = template.iterator();
		int object = 0;
		for (int y = 0; y < HEIGHT; y++) {
			for (int x = 0; x < WIDTH; x++) {
				final int offset = object * VERT_PER_OBJ * 3;
				final float z = (float) Math.sin(time + x + y) * 3 + 3;
				final Vector[] orig = iterator.next();

				final float alpha = x / (float) WIDTH;
				final float curve = (float) Math.sin(alpha * 5f + time * 0.5f) * 12 - 10;

				for (int i = 0; i < 6; i++) {
					vertices.put(offset + 3 * (6 + i) + 2, z - SLANT_HEIGHT);
					vertices.put(offset + 3 * (12 + i) + 2, z);

					vertices.put(offset + 3 * (0 + i) + 1, orig[i].y + curve);
					vertices.put(offset + 3 * (6 + i) + 1, orig[i].y + curve);
					vertices.put(offset + 3 * (12 + i) + 1, orig[i].y + curve);
				}
				vertices.put(offset + 3 * VERT_PER_OBJ - 1, z);
				vertices.put(offset + 3 * VERT_PER_OBJ - 2, orig[6].y + curve);
				object++;
			}
		}
		vertices.rewind();
	}

	private void setupLight2(final GL2 gl) {
		gl.glDisable(GL2.GL_LIGHT0);
		gl.glDisable(GL2.GL_LIGHT1);
		gl.glLightfv(GL2.GL_LIGHT2, GL2.GL_AMBIENT, ambient, 0);
		gl.glLightfv(GL2.GL_LIGHT2, GL2.GL_DIFFUSE, diffuse, 0);
		gl.glLightfv(GL2.GL_LIGHT2, GL2.GL_SPECULAR, specular, 0);
		gl.glLightf(GL2.GL_LIGHT2, GL2.GL_CONSTANT_ATTENUATION, .75f);
		gl.glLightfv(GL2.GL_LIGHT2, GL2.GL_POSITION, light2_pos, 0);
		gl.glEnable(GL2.GL_LIGHT2);
		gl.glEnable(GL2.GL_LIGHTING);
	}

	@Override
	public void destroy(final GLAutoDrawable drawable) {
	}
}
